"""
Markdown辅助工具，便于生成Markdown报告
 Created by luogang on 2020/8/10
"""
import inspect
import os
import re
import uuid

import matplotlib.pyplot as plt
import pandas as pd
from mdutils import MdUtils, TextUtils
from mdutils.tools.Header import Header
from mdutils.tools.Image import Image
from mdutils.tools.Link import Reference
from src.utils.logger import Logger


class MdUtilsExt(MdUtils):
    def __init__(self, file_name, title="", author=""):
        """
        原始类中，文档标题不能作为一级标题，直接添加二级标题会报错
        :param file_name: it is the name of the Markdown file.
        :type file_name: str
        :param title: it is the title of the Markdown file. It is written with Setext-style.
        :type title: str
        :param author: it is the author fo the Markdown file.
        :type author: str
        """
        self.file_name = file_name
        self.author = author
        self.header = Header()
        self.textUtils = TextUtils
        self.title = ''
        # self.title = self.header.choose_header(level=1, title=title)
        self.table_of_contents = ""
        self.file_data_text = ""
        self._table_titles = []
        self.reference = Reference()
        self.image = Image(reference=self.reference)
        self.new_header(1, title)


class MarkdownMixin:
    def __init__(self, file_name, title, report_dir, logger: Logger = None):
        self.md = MdUtilsExt(file_name=file_name, title=title)
        self.logger = logger
        self._table_details = {}
        self.report_dir = report_dir

    def report_list(self, lst):
        self.md.new_list(lst)

    def report_kv(self, kv, label=None, use_title=False):
        if label is not None:
            if use_title:
                self.md.new_header(level=3, title=label)
            else:
                self.md.new_paragraph(label)
        for key in kv:
            self.logger.info(f"{key}: {str(kv[key])}")

        self.md.new_list([f'{key}: {str(value)}' for key, value in kv.items()])

    def report_note(self, text, title=None):
        """
        在Markdown报告中添加说明，具体会以Markdown的quote形式实现
        :param text:
        :param title:
        :return:
        """
        if title is not None:
            title = f'>**{title}: **'
        else:
            title = '>'
        self.md.new_paragraph(f'{title}{text}')

    def report_series(self, pd_series, title):
        df = pd.DataFrame({'指标': pd_series.index, '值': pd_series.values})
        self.report_table(df, title)

    def report_text(self, text):
        self.md.new_paragraph(str(text))

    def preview_data(self, df, title='数据预览', limit=20, randomize=False):
        self.report_table(df, title=title, limit=limit, disable_save=True, randomize=randomize)

    def convert_series(self, series):
        return pd.DataFrame({'指标': series.index, '值': series.values})

    def report_table(self,
                     df,
                     title=None,
                     limit=100,
                     display_all=False,
                     disable_save=False,
                     as_attachment=False,
                     display_title=True,
                     randomize=False):
        """
        在报告中添加表格数据
        :param df: 待添加的表格数据
        :param title: 表格说明
        :param limit: 限制显示的行数
        :param display_all: 是否即使超过显示显示的行数依然强制显示全部
        :param disable_save: 是否禁止保存额外的Excel文件
        :param as_attachment: 是否无论显示内容多少都额外保存一份Excel文件
        :param randomize: 当不能全部显示时，是否随机显示
        :return:
        """

        if display_title and title is not None:
            self.md.new_paragraph(f'**{title}**')

        if len(df) == 0:
            self.logger.warn(f'dataframe空')
            self.logger.warn(df.columns.tolist())
            self.md.new_paragraph(
                f"数据量: {len(df):,}，列数: {df.shape[1]}，表格列名：{', '.join(['`' + item + '`' for item in df.columns])}"
            )
            return
        else:
            self.md.new_paragraph(f"数据量: {len(df):,}，列数: {df.shape[1]}")

        self.md.new_paragraph()
        if disable_save and len(df) > limit:
            self.md.new_paragraph(f"> 仅展示{'随机选择的' if randomize else ''}前{limit}条记录")
            self.md.new_paragraph()

        if len(df) < limit or display_all:
            df_sample = df
            if as_attachment and not disable_save:
                if title is None:
                    title = str(uuid.uuid4())
                self.md.new_paragraph(f'> **说明:** 也可以查看`{title}.csv`')
                self._add_table_details(title, df)
                self.md.new_paragraph()
        else:
            df_sample = df[:limit]
            if not disable_save:
                if title is None:
                    title = str(uuid.uuid4())
                self.md.new_paragraph(
                    f"> **说明:** 数据量超过{limit}，仅展示{'随机选择的' if randomize else ''}前{limit}条记录，详情请查看`{title}.csv`")
                self._add_table_details(title, df)
                self.md.new_paragraph()

        content_list = df_sample.columns.tolist()
        content_list.extend([
            f'{cell:6f}' if type(cell).__name__ == 'float' else str(cell) for row in df_sample.values
            for cell in row
        ])
        self.md.new_table(columns=df_sample.shape[1], rows=df_sample.shape[0] + 1, text=content_list)

    def report_matplot(self, title, hide_title=True):
        img_relpath = f'./media/{title}-{str(uuid.uuid4())}.png'
        plt.savefig(os.path.join(self.report_dir, img_relpath), transparent=True)
        plt.close()
        if not hide_title:
            self.md.new_paragraph(title)
        self.md.new_line(self.md.new_inline_image(title, img_relpath))

    def report_image(self, title, filepath):
        self.md.new_line(self.md.new_inline_image(title, filepath))

    def report_h2(self, title):
        self.md.new_header(level=2, title=title)

    def report_h3(self, title):
        self.md.new_header(level=3, title=title)

    def report_h4(self, title):
        self.md.new_header(level=4, title=title)

    def report_h5(self, title):
        self.md.new_header(level=5, title=title)

    def report_method(self, method, description=None):
        code = inspect.getsource(method)
        # 统一减少4个空格的缩进
        code = re.sub('^    ', '', code)
        code = re.sub('\n    ', '\n', code)
        code = code.strip()
        self.report_code(code, lang='python', description=description)

    def report_code(self, code, lang='python', description=None):
        if description is not None:
            self.md.new_paragraph(f'**{description}**')
        self.md.insert_code(code, language=lang)

    def report_sql(self, sql, description=None):
        self.report_code(sql, lang='sql', description=description)

    def _add_table_details(self, name, df, is_series=False):
        """
        添加DataFrame详情，调用此函数后，会将详情作为单独的Excel文件输出；子类不建议直接调用此方法，使用report_table方法添加DataFrame，这样会在报告中预览数据，还可以控制是否输出文件
        :param name:
        :param df:
        :param is_series:
        :return:
        """
        if is_series:
            self._table_details[name] = pd.DataFrame({'指标': df.index, '值': df.values})
        else:
            self._table_details[name] = df

    def write_report(self):
        """
        保存报告，包括Markdown格式的总报告和在详情表格
        :return:
        """
        # self.md.new_table_of_contents(table_title='目录', depth=2)
        self.md.create_md_file()
        for filename in self._table_details:
            filepath = os.path.join(self.report_dir, f'{filename}.csv')
            self.logger.info('正在输出详细数据:', filepath)
            df = self._table_details[filename]
            df.to_csv(filepath, index=False, quotechar='"')


if __name__ == '__main__':
    pass